#ifndef _ARRAY_H_
#define _ARRAY_H_

#include "./container.h"
#include <deque>

namespace Holders {

	class OutOfBoundsException : public Exception {
	protected:
		usint bound_index;
		usint access_index;

	public:
		OutOfBoundsException( const string &message, const usint &broken_bound, const usint &accessed_position, Exception *innerException = NULL )
			: Exception( message, innerException ), bound_index(broken_bound), access_index(accessed_position) {
#ifdef ALLOW_EXCEPT
			cout << (*this) << endl;
#endif
		};
		OutOfBoundsException( const OutOfBoundsException &original )
			: Exception( original ), bound_index(original.bound_index), access_index(original.access_index) {
#ifdef ALLOW_EXCEPT
			cout << (*this) << endl;
#endif
		};
		~OutOfBoundsException(){
			Exception::~Exception();
		};

		inline usint getBound() const {
			return bound_index;
		};

		inline usint getAccess() const {
			return access_index;
		};

		// --- Writeable ---
		virtual bool filePrint( FILE *parq = stdin ) const {
			return false;
		};

		virtual inline string toS() const {
			return Exception::toS() + "\nAccessing index " + access_index + " of " + bound_index + ".";
		};
		// --- Classificable ---

		static inline string ClassName() {
			return "OutOfBoundsException";
		};

		virtual inline string className() const {
			return ClassName();
		};

	};

	class MemoryException : public Exception {
	protected:
		usint obj_size;
		usint quantity;

	public:
		MemoryException( const string &message, const usint &object_size, const usint &quantity = 1, Exception *innerException = NULL )
			: Exception( message, innerException ), obj_size(object_size), quantity(quantity) {
#ifdef ALLOW_EXCEPT
			cout << (*this) << endl;
#endif
		};
		MemoryException( const MemoryException &original )
			: Exception( original ), obj_size(original.obj_size), quantity(original.quantity) {
#ifdef ALLOW_EXCEPT
			cout << (*this) << endl;
#endif
		};
		~MemoryException(){
			Exception::~Exception();
		};

		inline usint getObjectSize() const {
			return obj_size;
		};
		inline usint getQuantity() const {
			return quantity;
		};

		// --- Writeable ---
		virtual bool filePrint( FILE *parq = stdin ) const {
			return false;
		};

		virtual inline string toS() const {
			return Exception::toS() + "\nTrying to allocate " + quantity + ( quantity == 1 ? " object " : " objects " ) + "with size " + obj_size + " bytes.";
		};
		// --- Classificable ---

		static inline string ClassName() {
			return "MemoryException";
		};

		virtual inline string className() const {
			return ClassName();
		};

	};

	//! Default function/method debug
	/*!
	*/
	template< class Type >
		class Array: public Container {
		protected:
			deque< Type > *list;

		public:

			typedef typename deque<Type>::iterator Iter;

			Array<Type>() : list(NULL) {
				list = new deque< Type >();
			}
			Array<Type>( const Array<Type> &original ) : list(NULL) {
				list = new deque< Type >();
				copy( original );
			};
			virtual ~Array<Type>(){
				if( list != NULL ) {
					list->clear();
					delete list;
					list = NULL;
				}
			}

			inline void copy( const Array<Type> &original ){
				for( Iter i = original.list->begin(); i != original.list->end(); ++i )
					this->list->push_back( *i );
			}
			inline void insert( const Type &obj ){
				list->push_back( obj );
			};
			inline void empty() {
				list->empty();
			};

			inline Type at( const usint &index ) const {
				return (*this)[index];
			};

			inline usint size() const {
				return (usint) list->size();
			};

			inline Iter begin() const {
				return list->begin();
			};

			inline Iter end() const {
				return list->end();
			};

			inline Array<Type>& operator, ( const Type &obj ){
				insert( obj );
				return (*this);
			};

			inline Type& operator [] ( const usint &index ) const {
				if( index >= list->size() )
					throw OutOfBoundsException( "Invalid index in this Array<Type> object.", list->size(), index );
				return (*list)[index];
			};

			bool operator == ( const Array<Type> &obj ) const {
				if( this->size() != obj.size() )
					return false;
				for( usint i = 0; i < size(); ++i )
					if( list->at(i) != obj.list->at(i) )
						return false;
				return true;
			};

			inline bool operator != ( const Array<Type> &obj ) const {
				return ! ( (*this) == obj );
			}

			inline Array<Type>& operator = ( const Array<Type> &obj ) {
				list->clear();
				for( Iter i = obj.list->begin(); i != obj.list->end(); ++i )
					insert( *i );
				return (*this);
			};

			inline Array<Type>& operator += ( const Array<Type> &obj ) {
				for( Iter i = obj.list->begin(); i != obj.list->end(); ++i )
					insert( *i );
				return (*this);
			};

			inline Array<Type> operator + ( const Array<Type> &obj ) const {
				Array<Type> result;
				return ( ( result += (*this) ) += obj );
			};

			Type* AsPointerArray() const {
				Type *ptr_list = (Type*) malloc( list->size() * sizeof( Type ) );
				if( ptr_list == NULL )
					throw MemoryException("Couldn't allocate memory for the array.", sizeof( Type ), (usint) list->size());

				for( usint i = 0; i < list->size(); ++i )
					ptr_list[i] = (*list)[i];

				return ptr_list;
			};

			// --- Writeable ---
			// TODO - filePrint
			virtual bool filePrint( FILE *parq = stdin ) const {
				return false;
			};

			virtual inline string toS() const {
				string result = "[";
				for( Iter i = this->list->begin(); i != this->list->end(); ++i )
					result += string(" ") + (*i);
				return result + " ]";
			};

			// --- Classificable ---

			static inline string ClassName() {
				return "Array";
			};

			virtual inline string className() const {
				return ClassName();
			};

		};

};

#endif // _ARRAY_H_
